/**
 * 
 */
package matrix;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * @author Yoni Seroussi
 * @author Roei Raviv
 * 
 * All matrices are same order, and #row == #col (i.e. each mat is of NxN order)
 */
/**
 * @author Yoni Seroussi
 * @author Roei Raviv
 * 
 */
public class MatricesMultiplier {

    // matrices to multiply in the order of multiplication.
    private ArrayList<double[][]> _matricesList;
    private int _order; // order of matrices
//    private int _currentMatrixIdx;
    private volatile double[][] _result; //might needed to be volatile

    /**
     * Assume matricesList has at least 3 matrices
     * 
     * @param matricesList
     * @param order
     */
    public MatricesMultiplier(ArrayList<double[][]> matricesList, int order) {
        _matricesList = matricesList;
        _order = order;
//        _currentMatrixIdx = 0;
        _result = new double[order][order];
        
        // set result to the identity matrix (we could put the first matrix but 
        // we don't mind "wasting time" on the first mult.
        for ( int i = 0 ; i < order ; ++i ) {
            _result[i][i] = 1;
        }
    }

    public ArrayList<double[][]> getMatricesList() {
        return _matricesList;
    }

    public void setMatricesList(ArrayList<double[][]> matricesList) {
        _matricesList = matricesList;
    }

    public int getOrder() {
        return _order;
    }

    public void setOrder(int order) {
        _order = order;
    }

    /**
     * @return The matrix we now multiply
     */
    public double[][] getMatrixToMult(int index) {
        return _matricesList.get(index);
    }
    
 
//
//    /**
//     * Increments counter to the next matrix id.
//     */
//    public void advanceToNextMatrix() {
//        if (_currentMatrixIdx == _matricesList.size() - 1) {
//            throw new IllegalStateException("trying to get next matrix, "
//                    + "but there are no more");
//        }
//        _currentMatrix = _matricesList.get(++_currentMatrixIdx);
//    }

    /**
     * @return the number of matrices in _matricesList.
     */
    public int getNumberOfMatrices() {
        return _matricesList.size();
    }
    
    /**
     * @param index
     * @return
     */
    public double[] getResultMatrixRow(int index) {
        return getRowOrColFromResult(true, index);
    }
    
    /**
     * @param index
     * @return
     */
    public double[] getResultMatrixColumn(int index) {
        return getRowOrColFromResult(false, index);
    }
    
    /**
     * @param isRow true iff given index is a row index on result matrix,
     * false if index is column index.
     * @param index index of column or row to return from result matrix
     * @return col/row[index]
     */
    private double[] getRowOrColFromResult(boolean isRow, int index) {
        double[] ret = new double[_order]; // we want a copy not a referance.
        if (isRow) {
            ret = Arrays.copyOf(_result[index], _result[index].length);
        } else {
            for (int i = 0 ; i < _order ; ++i) {
                ret[i] = _result[i][index]; 
            }
        }
        return ret;
    }
    
    public void setResultElem(int row, int col, double val) {
        _result[row][col] = val;
    }
    
    
    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public StringBuffer getResultAsStringBuf() {
        StringBuffer buf = new StringBuffer("\nResult Matrix:\n");
        for (int i = 0; i < _order; i++) {
            for (int j = 0; j < _order; j++) { 
                buf.append(_result[i][j]).append(", ");
            }
            buf.replace(buf.lastIndexOf(","), buf.length()-1, "\n");
        }
        return buf;
    }

    
    /**
     * Single thread multiplication of given matrices
     * 
     * @param ord
     * @return
     */
    public double[][] multAB(int ord){
        double[][] res = new double[ord][ord];
        for (int i = 0; i < res.length; i++) {
            res[i][i] = 1.0;
        }
        
        for (double[][] mat : _matricesList) {
            double[][] tmp = new double[ord][ord]; 
            for (int i = 0; i < mat.length; i++) {
                for (int k = 0 ; k < mat.length ; k ++) {
                    double sum = 0;
                    for (int j = 0; j < mat.length; j++) {
                        sum += res[i][j] * mat[j][k];
                    }
                    tmp[i][k] = sum;
                }
            }
            res = tmp;
            
        }
        
        return res;
    }
    
    /**
     * @param mat matrix to print (if null - print _result)
     */
    public void printMatrix(double[][] mat) { 
        if (mat == null) {
            mat = _result;
        }
        int order = mat[0].length;
        for (int i = 0 ; i < order ; ++i) {
            for (int j = 0 ; j < order ; ++j) {
                System.out.print(mat[i][j]+" ");
            }
            System.out.println();
        }
    }
}
